UDP编程流程(UDP客户端、服务器互发消息流程)

您所在的位置:网站首页 udp sendto函数没有返回和发送缓冲区的大小有关吗 UDP编程流程(UDP客户端、服务器互发消息流程)

UDP编程流程(UDP客户端、服务器互发消息流程)

2024-07-10 14:29| 来源: 网络整理| 查看: 265

一、UDP编程流程 1.1、 UDP概述

UDP,即用户数据报协议,是一种面向无连接的传输层协议。相比于TCP协议,UDP具有以下特点:

速度较快:由于UDP不需要建立连接和进行复杂的握手过程,因此在传输数据时速度稍快于TCP协议。适用于简单的请求/应答应用程序:对于一些简单的、对可靠性要求不高的应用程序,如DNS查询和SNMP请求,UDP能够提供高效的传输服务。不适用于海量数据传输:由于UDP不提供可靠的数据传输机制,因此在进行大数据传输时容易出现丢包和乱序的情况,不建议使用UDP进行此类传输。广播和多播应用必须使用UDP:UDP支持广播和多播传输,因此对于需要进行广播和多播的应用,如视频直播和组播通信,必须使用UDP协议。

UDP应用:

UDP协议被广泛应用于各种网络应用中,包括但不限于以下几种:

DNS(域名解析):DNS使用UDP进行域名解析请求和响应的传输,以提高查询速度。NFS(网络文件系统):NFS使用UDP进行文件操作请求和响应的传输,以提高文件访问速度。RTP(实时传输协议):RTP使用UDP进行实时音视频数据的传输,以减少延迟。

此外,一些实时性要求较高的应用,如在线游戏和VoIP通话,也会选择使用UDP协议来减少延迟,提高用户体验

1.2、网络编程接口socket

Socket,也被称为"套接字",是网络编程中用于实现不同主机上进程间通信的一种技术。它提供了一种将网络通信抽象为文件操作的接口,使得程序员可以通过简单的函数调用来实现复杂的网络通信功能。

Socket的特点

文件描述符:Socket是一种文件描述符,它代表了一个通信管道的端点。通过Socket,我们可以像操作文件一样,使用read、write、close等函数来发送和接收网络数据。通信端点:Socket是网络通信的端点,每个Socket都有一个唯一的地址,通过这个地址,我们可以与远程主机上的Socket进行通信。网络数据操作:通过Socket,我们可以方便地进行网络数据的发送和接收。Socket提供了丰富的函数接口,如send、recv、sendto、recvfrom等,用于各种网络数据操作。Socket函数:要获得一个Socket,我们需要调用socket()函数。该函数返回一个Socket描述符,用于后续的网络通信操作。

Socket的分类

根据使用的协议和通信方式的不同,Socket可以分为以下几种类型:

SOCK_STREAM:流式套接字,用于TCP协议。它提供了可靠的、面向连接的通信方式,数据以流的形式传输,可以保证数据的可靠性和顺序性。SOCK_DGRAM:数据报套接字,用于UDP协议。它提供了不可靠、无连接的通信方式,数据以数据报的形式传输,不保证数据的可靠性和顺序性。SOCK_RAW:原始套接字,用于其他层次的协议操作。它允许直接访问网络层数据,可以用于实现自定义的网络协议。 1.3 UDP编程C/S架构

UDP网络编程流程:

服务器: 创建套接字 socket( )

                将服务器的ip地址、端口号与套接字进行绑定 bind( )

                接收数据 recvfrom()

                发送数据 sendto()

客户端:  创建套接字 socket()

                发送数据 sendto()

                接收数据 recvfrom()

                关闭套接字 close() 

二、UDP编程-创建套接字  2.1 创建socket套接字 #include #include int socket(int domain, int type, int protocol); 功能: 创建一个套接字,返回一个文件描述符 参数: domain:通信域,协议族 AF_UNIX 本地通信 AF_INET ipv4网络协议 AF_INET6 ipv6网络协议 AF_PACKET 底层接口 type: 套接字的类型 SOCK_STREAM 流式套接字(tcp) SOCK_DGRAM 数据报套接字(udp) SOCK_RAW 原始套接字(用于链路层) protocol: 附加协议,如果不需要,则设置为0 返回值: 成功:文件描述符 失败:-1

特点 

 创建套接字时,系统不会分配端口  创建的套接字默认属性是主动的,即主动发起服务的请求;当作为服务器时,往往需要修改为被动的  2.2举例创建套接字 #include #include #include #include int main(int argc, char const *argv[]) { // 使用socket函数创建套接字 // 创建一个用于UDP网络编程的套接字 int sockfd; if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror("fail to socket"); exit(1); } printf("sockfd = %d\n", sockfd); return 0; }

使用socket()函数创建了一个UDP类型的套接字,并打印出该套接字的文件描述符。如果创建套接字失败,则会输出错误信息并退出程序。

 执行结果

三、UDP编程-发送、绑定、接收数据  3.1 IPv4套接字地址结构

在网络编程中经常使用的结构体

头文件:#include

struct in_addr { in_addr_t s_addr;//ip地址 4字节 }; struct sockaddr_in { sa_family_t sin_family;//协议族 2字节 in_port_t sin_port;//端口号 2字节 struct in_addr sin_addr;//ip地址 4字节 char sin_zero[8]//填充,不起什么作用 8字节 };

为了使不同格式地址能被传入套接字函数,地址须要强制转换成通用套接字地址结构,原因是因为不同场合所使用的结构体不一样,但是调用的函数却是同一个,所以定义一个通用结构体,当在指定场合使用时,在根据要求传入指定的结构体即可

通用结构体 sockaddr

头文件:#include 

struct sockaddr { sa_family_t sa_family; // 2字节 char sa_data[14] //14字节 }; 3.2 两种地址结构使用场合

在定义源地址和目的地址结构的时候,选用struct sockaddr_in;

例:

struct  sockaddr_in  my_addr;

当调用编程接口函数,且该函数需要传入地址结构时需要用struct sockaddr进行强制转换

例:

bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr));

3.3 发送数据—sendto函数  #include #include ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

功能: sendto函数用于通过套接字发送数据。

参数:

sockfd:文件描述符,由socket函数返回。buf:指向要发送数据的缓冲区的指针。len:要发送的数据的长度。flags:标志位,用于指定发送操作的行为。常见的标志位有: 0:阻塞模式,函数会一直等待直到数据发送完成。MSG_DONTWAIT:非阻塞模式,如果数据不能立即发送,函数会立即返回错误。dest_addr:指向目的网络信息结构体的指针,用于指定数据发送的目标地址。addrlen:目的网络信息结构体的长度。

返回值:

成功:返回发送的字节数。失败:返回-1,并设置errno以指示错误原因。

sendto函数是网络编程中常用的函数之一,它允许我们向指定的目标地址发送数据。在使用该函数时,我们需要正确地指定目的地址和地址长度,以确保数据能够准确地发送到目标。同时,我们也需要注意选择合适的标志位,以满足不同的发送需求。

 3.4 向“网络调试助手”发送消息

我们用一个“网络调试助手”的软件来模拟服务器,给服务器发送消息。

客户端的代码编写

#include //printf #include //exit #include #include //socket #include //sockaddr_in #include //htons inet_addr #include //close #include #define N 128 int main(int argc, char const *argv[]) { //./a.out 192.168.3.78 8080 if(argc < 3) { fprintf(stderr, "Usage:%s ip port\n", argv[0]); exit(1); } //第一步:创建套接字 int sockfd; if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror("fail to socket"); exit(1); } printf("sockfd = %d\n", sockfd); //第二步:填充服务器网络信息结构体 sockaddr_in struct sockaddr_in serveraddr; socklen_t addrlen = sizeof(serveraddr); serveraddr.sin_family = AF_INET; //协议族,AF_INET:ipv4网络协议 serveraddr.sin_addr.s_addr = inet_addr(argv[1]); //ip地址 serveraddr.sin_port = htons(atoi(argv[2])); //第三步:发送数据 char buf[N] = ""; while(1) { fgets(buf, N, stdin); buf[strlen(buf) - 1] = '\0'; //把buf字符串中的\n转化为\0 if(sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&serveraddr, addrlen) == -1) { perror("fail to sendto"); exit(1); } } //第四步:关闭套接字文件描述符 close(sockfd); return 0; }

执行结果 

 3.5 绑定--bind函数

UDP网络程序想要收取数据需什么条件?

确定的ip地址

确定的port

怎样完成上面的条件呢?

接收端 使用bind函数,来完成地址结构与socket套接字的绑定,这样ip、port就固定 了 发送端 在sendto函数中指定接收端的ip、port,就可以发送数据了

 由于服务器是被动的,客户端是主动的,所以一般先运行服务器,后运行客户端,所以服务 器需要固定自己的信息(ip地址和端口号),这样客户端才可以找到服务器并与之通信,但 是客户端一般不需要bind绑定,因为系统会自动给客户端分配ip地址和端口号

#include #include int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 功能: 将套接字与网络信息结构体绑定 参数: sockfd:文件描述符,socket的返回值 addr:网络信息结构体 通用结构体(一般不用) struct sockaddr 网络信息结构体 sockaddr_in #include struct sockaddr_in addrlen:addr的长度 返回值: 成功:0 失败:-1 3.6 bind示例 //第二步:将服务器的网络信息结构体绑定前进行填充 struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); serveraddr.sin_port = htons(atoi(argv[2])); //第三步:将网络信息结构体与套接字绑定 if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) { perror("fail to bind"); exit(1); } 3.7 接收数据—recvfrom 函数 #include #include ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); 功能: 接收数据 参数: sockfd:文件描述符,socket的返回值 buf:保存接收的数据 len:buf的长度 flags:标志位 0 阻塞 MSG_DONTWAIT 非阻塞 src_addr:源的网络信息结构体(自动填充,定义变量传参即可) addrlen:src_addr的长度 返回值: 成功:接收的字节数 失败:-1 3.8 接收“网络调试助手”的数据

此时我们把刚刚也用到过的网络调试助手作为客户端,ubuntu的程序作为服务器

设置服务器(ubuntu程序) 

#include //printf #include //exit #include #include //socket #include //sockaddr_in #include //htons inet_addr #include //close #include #define N 128 int main(int argc, char const *argv[]) { if(argc < 3) { fprintf(stderr, "Usage: %s ip port\n", argv[0]); exit(1); } //第一步:创建套接字 int sockfd; if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { perror("fail to socket"); exit(1); } //第二步:将服务器的网络信息结构体绑定前进行填充 struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.3.103 serveraddr.sin_port = htons(atoi(argv[2])); //9999 //第三步:将网络信息结构体与套接字绑定 if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) { perror("fail to bind"); exit(1); } //接收数据 char buf[N] = ""; struct sockaddr_in clientaddr; socklen_t addrlen = sizeof(struct sockaddr_in); while(1) { if(recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&clientaddr, &addrlen) == -1) { perror("fail to recvfrom"); exit(1); } //打印数据 //打印客户端的ip地址和端口号 printf("ip:%s, port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); //打印接收到数据 printf("from client: %s\n", buf); } return 0; }

执行结果

 四、UDP编程-client、server

接下来我们就可以自己实现服务器与客户端互发消息了

4.1 UDP客户端

1、本地IP、本地端口(我是谁)

2、目的IP、目的端口(发给谁)

3、在客户端的代码中,我们只设置了目的IP、目的端口

客户端的本地ip、本地port是我们调用sendto的时候linux系统底层自动给客户端分配 的;分配端口的方式为随机分配,即每次运行系统给的port不一样

//udp客户端的实现 #include //printf #include //exit #include #include //socket #include //sockaddr_in #include //htons inet_addr #include //close #include int main(int argc, char const *argv[]) { if(argc < 3) { fprintf(stderr, "Usage: %s \n", argv[0]); exit(1); } int sockfd; //文件描述符 struct sockaddr_in serveraddr; //服务器网络信息结构体 socklen_t addrlen = sizeof(serveraddr); //第一步:创建套接字 if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("fail to socket"); exit(1); } //客户端自己指定自己的ip地址和端口号,一般不需要,系统会自动分配 #if 0 struct sockaddr_in clientaddr; clientaddr.sin_family = AF_INET; clientaddr.sin_addr.s_addr = inet_addr(argv[3]); //客户端的ip地址 clientaddr.sin_port = htons(atoi(argv[4])); //客户端的端口号 if(bind(sockfd, (struct sockaddr *)&clientaddr, addrlen) < 0) { perror("fail to bind"); exit(1); } #endif //第二步:填充服务器网络信息结构体 //inet_addr:将点分十进制字符串ip地址转化为整形数据 //htons:将主机字节序转化为网络字节序 //atoi:将数字型字符串转化为整形数据 serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); serveraddr.sin_port = htons(atoi(argv[2])); //第三步:进行通信 char buf[32] = ""; while(1) { fgets(buf, sizeof(buf), stdin); buf[strlen(buf) - 1] = '\0'; if(sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) { perror("fail to sendto"); exit(1); } char text[32] = ""; if(recvfrom(sockfd, text, sizeof(text), 0, (struct sockaddr *)&serveraddr, &addrlen) < 0) { perror("fail to recvfrom"); exit(1); } printf("from server: %s\n", text); } //第四步:关闭文件描述符 close(sockfd); return 0; } 4.3 UDP服务器

1、服务器之所以要bind是因为它的本地port需要是固定,而不是随机的

2、服务器也可以主动地给客户端发送数据

3、客户端也可以用bind,这样客户端的本地端口就是固定的了,但一般不这样做

//udp服务器的实现 #include //printf #include //exit #include #include //socket #include //sockaddr_in #include //htons inet_addr #include //close #include int main(int argc, char const *argv[]) { if(argc < 3) { fprintf(stderr, "Usage: %s \n", argv[0]); exit(1); } int sockfd; //文件描述符 struct sockaddr_in serveraddr; //服务器网络信息结构体 socklen_t addrlen = sizeof(serveraddr); //第一步:创建套接字 if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("fail to socket"); exit(1); } //第二步:填充服务器网络信息结构体 //inet_addr:将点分十进制字符串ip地址转化为整形数据 //htons:将主机字节序转化为网络字节序 //atoi:将数字型字符串转化为整形数据 serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]); serveraddr.sin_port = htons(atoi(argv[2])); //第三步:将套接字与服务器网络信息结构体绑定 if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0) { perror("fail to bind"); exit(1); } while(1) { //第四步:进行通信 char text[32] = ""; struct sockaddr_in clientaddr; if(recvfrom(sockfd, text, sizeof(text), 0, (struct sockaddr *)&clientaddr, &addrlen) < 0) { perror("fail to recvfrom"); exit(1); } printf("[%s - %d]: %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), text); strcat(text, " *_*"); if(sendto(sockfd, text, sizeof(text), 0, (struct sockaddr *)&clientaddr, addrlen) < 0) { perror("fail to sendto"); exit(1); } } //第四步:关闭文件描述符 close(sockfd); return 0; }

执行的结果



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3